<?php
/**
/*  vim:ts=4:expandtab:cindent
 *  This file is part of Urd.
 *
 *  Urd is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 3 of the License, or
 *  (at your option) any later version.
 *  Urd is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program. See the file "COPYING". If it does not
 *  exist, see <http://www.gnu.org/licenses/>.
 *
 * $LastChangedDate: 2009-01-03 23:44:12 +0100 (Sat, 03 Jan 2009) $
 * $Rev: 410 $
 * $Author: gavinspearhead $
 * $Id: urdd.php 410 2009-01-03 22:44:12Z gavinspearhead $
 */
define('ORIGINAL_PAGE', 'URDD');

$pathu = realpath(dirname(__FILE__));

$process_name = 'urdd'; // needed for syslog and logging

require_once "$pathu/../functions/autoincludes.php";
require_once "$pathu/../functions/urdversion.php";
require_once "$pathu/../functions/defines.php";
require_once "$pathu/../functions/error_codes.php";
require_once "$pathu/../functions/defaults.php";
require_once "$pathu/../config.php";
require_once "$pathu/../functions/functions.php";
require_once "$pathu/../functions/mail_functions.php";
require_once "$pathu/urdd_functions.php";
require_once "$pathu/queue_functions.php";
require_once "$pathu/show_functions.php";
require_once "$pathu/do_functions.php";
require_once "$pathu/post_functions.php";
require_once "$pathu/download_functions.php";
require_once "$pathu/../functions/urd_log.php";
require_once "$pathu/urdd_command.php";
require_once "$pathu/urdd_config.php";
require_once "$pathu/urdd_help.php";
require_once "$pathu/urdd_test.php";
require_once "$pathu/urdd_rss.php";
require_once "$pathu/urdd_error.php";
require_once "$pathu/urdd_options.php";
require_once "$pathu/urdd_protocol.php";
require_once "$pathu/../functions/lang/english.php";


function daemonise() // changes cwd to root!
{
    echo_debug_function(DEBUG_MAIN, __FUNCTION__);
    $pid = pcntl_fork();
    if ($pid < 0) {  // error
        write_log('Daemonise failed', LOG_ERR);
        exit(INTERNAL_ERROR) ;
    } else if ($pid != 0) // parent
        exit(NO_ERROR) ; // parent exits
    else { // child continues
        posix_setsid();
        chdir('/'); // set the working dir otherwise an umount might fail if we run start from some dir
        set_error_handler('urdd_error_handler'); // needed as we close stderr... and php will crash if something writes to stderr/out
        fclose(STDERR); // don't generate any output
        fclose(STDOUT); // don't generate any output
        umask(0);
    }
}


function update_user_last_seen_group(DatabaseConnection $db, $group_id)
{
    assert(is_numeric($group_id));
    echo_debug_function(DEBUG_WORKER, __FUNCTION__);
    $sql = 'SELECT "last_login", "ID" FROM users';
    $res = $db->execute_query($sql);
    if ($res === FALSE) {
        write_log('Cannot update user group settings; no data found', LOG_NOTICE);
        return FALSE;
    }
    $users = array();
    $now = time();
    foreach ($res as $row) {
        $sql = "UPDATE usergroupinfo SET \"last_update_seen\" = '$now' " 
            . "WHERE \"userid\" = '{$row['ID']}' AND \"groupid\" = '$group_id' AND \"last_update_seen\" < {$row['last_login']} ";
        $db->execute_query($sql);
    }
    return TRUE;
}


function handle_queue_item(DatabaseConnection $db, action $item)
{
    echo_debug_function(DEBUG_WORKER, __FUNCTION__);
    global $commands_list, $config;
    $rv = NO_ERROR;
    $cmd_code = $item->get_command_code();
    if (get_command($cmd_code) === FALSE)
        exit (INTERNAL_FAILURE);
    
    if ($config['disable_nntp'] === TRUE && $cmd->needs_nntp() ) {
        $response = $responses[410];
        return URDD_NOERROR;
    }

    static $cmd_table = array(
        COMMAND_ADDDATA =>'do_adddata',
        COMMAND_CHECK_VERSION =>'do_check_version',
        COMMAND_CLEANDB => 'do_cleandb',
        COMMAND_CLEANDIR =>'do_cleandir',
        COMMAND_DELETE_SET => 'do_delete_set',
        COMMAND_DELETE_SET_RSS => 'do_delete_set_rss',
        COMMAND_DOWNLOAD_ACTION =>'do_download',
        COMMAND_EXPIRE =>'do_expire',
        COMMAND_EXPIRE_RSS =>'do_expire_rss',
        COMMAND_FINDSERVERS => 'do_find_servers',
        COMMAND_GENSETS =>'do_gensets',
        COMMAND_GETSETINFO =>'do_getsetinfo',
        COMMAND_GROUPS =>'do_listupdate',
        COMMAND_MAKE_NZB =>'do_make_nzb',
        COMMAND_MERGE_SETS =>'do_merge_sets',
        COMMAND_OPTIMISE =>'do_optimise',
        COMMAND_PARSE_NZB =>'do_parse_nzb',
        COMMAND_POST =>'do_prepare_post',
        COMMAND_POST_ACTION =>'do_post_batch',
        COMMAND_POST_MESSAGE => 'do_post_message',
        COMMAND_PURGE =>'do_purge',
        COMMAND_PURGE_RSS =>'do_purge_rss',
        COMMAND_SENDSETINFO =>'do_sendsetinfo',
        COMMAND_UNPAR_UNRAR =>'do_unpar_unrar',
        COMMAND_UPDATE => 'do_update',
        COMMAND_UPDATE_RSS =>'do_update_rss'
    );
    if (isset($cmd_table[$cmd_code]))
        $rv = $cmd_table[$cmd_code]($db, $item);
    else {
        write_log('Error: unknown action');
        throw new exception('Error: unknown action', ERR_UNKNOWN_ACTION);
    }
    exit($rv);
}


function handle_crash(DatabaseConnection $db, server_data &$servers, action $item, $rc)
{
    assert(is_numeric($rc));
    echo_debug_function(DEBUG_MAIN, __FUNCTION__);
    write_log("Unknown exit status: $rc. Possible crash", LOG_NOTICE);
    $dbid = $id = $item->get_dbid();
    $db->escape($dbid, TRUE);
    if (compare_command($item->get_command(), COMMAND_DOWNLOAD_ACTION) && $servers->has_equal($item))
        return; // if there are more dl threads running, we do nothing
    if (compare_command($item->get_command(), COMMAND_POST_ACTION) && $servers->has_equal($item))
        return; // if there are more dl threads running, we do nothing
    $sql = "\"status\" FROM queueinfo WHERE \"ID\" = $dbid";
    $res = $db->select_query($sql, 1);
    if (isset ($res[0]['status']) && $res[0]['status'] == QUEUE_RUNNING) {
        echo_debug('Setting status to crashed', DEBUG_SERVER);
        update_queue_status($db, $id, QUEUE_CRASH, NULL, NULL, "Thread crashed ($rc)");
    }
}


function reap_children(DatabaseConnection $db, server_data &$servers)
{   
    /// xxx needs cleanup... too much crap here
    ///echo_debug_function(DEBUG_MAIN, __FUNCTION__);
    global $config, $commands, $test_results;
    try {
        while (($pid = pcntl_waitpid(-1, $status, WNOHANG)) > 0) { // we check if there is a signal
            $rc = pcntl_wexitstatus($status);
            echo_debug("Child exited (rc: $rc)", DEBUG_SERVER); 

            list ($item, $server_id, $status) = $servers->delete_thread($db, $pid, TRUE /*TODO check to store it?? */);
            echo_debug("Thread status $status; server: $server_id", DEBUG_SERVER);
            if ($rc == DB_FAILURE) {
                echo_debug('DB error received', DEBUG_SERVER);
                // push the action back on the queue if there was a problem with the database....
                $servers->queue_push($db, $item);
            } elseif ($rc == NNTP_NOT_CONNECTED_ERROR) {
                echo_debug('NNTP Connect error received', DEBUG_SERVER);
                if (compare_command($item->get_command(), COMMAND_DOWNLOAD_ACTION)) {// downloads are different as they can run on more servers
                    $servers->reschedule_download($db, $item, $server_id);
                }else if (compare_command($item->get_command(), COMMAND_POST_ACTION)) {// downloads are different as they can run on more servers
                    $servers->reschedule_post($db, $item, $server_id);
                } else {
                    $servers->reschedule($db, $item, $server_id);
                }
            } else if ($rc == DB_LOCKED) {
                reschedule_locked_item($db, $servers, $item);
            } else if ($rc == ENCRYPTED_RAR) {
                write_log('Cancelling download', LOG_NOTICE);
                $servers->delete_cmd($db, $item->get_username(), $item->get_command(), $item->get_args(), TRUE);
                // run cancel on download
                    // TODOD
            } else if ($rc == GROUP_NOT_FOUND) {
                echo_debug('Group not found', DEBUG_SERVER);
                // nothing to do really...
            } else if ($rc < 0) {
                echo_debug('A thread has crashed', DEBUG_SERVER);
                handle_crash($db, $servers, $item, $rc);
            } else {
                if ($rc != 0) 
                    echo_debug("Something weird happened ($rc). Finishing up thread.", DEBUG_SERVER);
                $cmd = $item->get_command();
                if ($status <= DOWNLOAD_ACTIVE) {
                    if (compare_command($cmd, COMMAND_DOWNLOAD_ACTION)) {
                        complete_download($db, $servers, $item, $status);
                    } elseif (compare_command($cmd, COMMAND_POST_ACTION)) {
                        complete_post($db, $servers, $item, $status);
                    } elseif (compare_command($cmd, COMMAND_POST)) {
                        queue_post($db, $servers,  $item->get_args(), $item->get_username(), DEFAULT_PRIORITY);
                    } elseif (compare_command($cmd, COMMAND_UPDATE)) {
                        queue_gensets($db, $servers, array($item->get_args()), $item->get_username(), DEFAULT_PRIORITY);
                    } elseif (compare_command($cmd, COMMAND_GENSETS)) {
                        if (get_config($db, 'auto_expire') == 1 && !$item->is_paused())
                            queue_purge_expire($db, COMMAND_EXPIRE, array($item->get_args()), $item->get_username(), $servers, DEFAULT_PRIORITY);
                    } elseif (compare_command($cmd, COMMAND_UPDATE_RSS)) {
                        if (get_config($db, 'auto_expire') == 1 && !$item->is_paused())
                            queue_purge_expire($db, COMMAND_EXPIRE_RSS, array($item->get_args()), $item->get_username(), $servers, DEFAULT_PRIORITY);
                    } elseif (compare_command($cmd, COMMAND_PARSE_NZB) && $rc == NO_ERROR) {
                        $args = split_args($item->get_args());
                        if (get_start_time($db, $args[0]) <= time()) // needed otherwise dl in browse with timestamp won't work,
                               //   but unpause is needed for other dls (spool, nzb import/upload)
                            $servers->pause_cmd($db, COMMAND_DOWNLOAD, $args[0], FALSE, $item->get_username());
                    } elseif (compare_command($cmd, COMMAND_FINDSERVERS)) {
                        restart_urdd($db, $servers);
                    }
                }
            }
        }
    } catch (exception $e) {
        write_log($e->getMessage(), LOG_ERR);
        echo_debug_trace($e, DEBUG_SERVER);
        exit(INTERNAL_FAILURE);
    }
}


function check_queue(DatabaseConnection &$par_db, conn_list $conn_list, server_data &$servers)
{
//  echo_debug_function(DEBUG_MAIN, __FUNCTION__);
    global $is_child, $config;

    reap_children($par_db, $servers);

    $item = $servers->get_first_runnable_on_queue($par_db);
    if ($item === FALSE || $item === TRUE)  // FALSE : no thread can run; TRUE: queue empty
        return $item;
    $command = $item->get_command_code();
    // if it is a download request, select the server it will run on then make n new queued download actions and run these on the server
    if ($command == COMMAND_DOWNLOAD) {
        create_download_threads($servers, $par_db, $item);
        return FALSE;
    } elseif ($command == COMMAND_START_POST) {
        create_post_threads($servers, $par_db, $item);
        return FALSE;
    } elseif ($command == COMMAND_ADDDATA) {
        if ($item->get_preview()) { // if no server is available or no total free slot, we simple force one
            if (!$servers->slots_available()) {
                try {
                    $servers->preempt($par_db, $item, $item->get_username());
                    usleep(5000);// wait so that the chld signal is delivered and the reap function calls it
                    return FALSE;
                } catch (exception $e) {
                    write_log('Cannot preempt', LOG_INFO);
                    return FALSE;
                }
            }
        }
    } elseif ($command == COMMAND_DOWNLOAD_ACTION) {
        if ($item->get_preview()) { // if no server is available or no total free slot, we simple force one
            $srv_id = $servers->find_free_slot($item->get_all_failed_servers(), FALSE); // is there a server that has a free slot
            $nntp_slots_available = $servers->nntp_slots_available(); // is there a total free slot
            if ($srv_id === FALSE || $nntp_slots_available === FALSE) {
                try {
                    $srv_id = $servers->preempt($par_db, $item, $item->get_username());
                    if ($srv_id == 0)
                        write_log('Server ID is 0 must be something wrong', LOG_WARNING);
                    $item->set_preferred_server($srv_id);
                    usleep(5000); // wait so that the chld signal is delivered and the reap function calls it
                    return FALSE;
                } catch (exception $e) {
                    write_log('Cannot preempt: ' . $e->getMessage(), LOG_INFO);
                    return FALSE;
                }
            } 
        }
    }
    // there is something to start

    if (get_command_primary_nntp($item->get_command())) {
        $srv_id = $servers->get_update_server();
        $item->set_preferred_server($srv_id);
        $item->set_active_server(0);
    }
    $pid = pcntl_fork();
    if ($pid < 0) { //error
        $status = QUEUE_FAILED;
        update_queue_status($par_db, $item->get_dbid(), $status);
        write_log('Error: fork failed');
        exit(INTERNAL_FAILURE);
    } elseif ($pid != 0) { // parent
        $cnt = 0;
        for(;;) { // connecting may be interrupted. We need to make sure that the connection is restored. 
            try {
                $par_db = connect_db();
                break;
            } catch (exception $e) {
                $cnt++;
                usleep ($cnt * 100); // sleep a bit but not too much as urdd gets unresponsive then
                if ($cnt == 1000)
                    throw $e; // something really bad must be wrong here
            }
        }

        try {
            $servers->add_thread($par_db, new thread($pid, $item));
            echo_debug("worker forked: PID $pid", DEBUG_SERVER);
            $servers->queue_delete($par_db, $item->get_id(), 'root'); // remove the item from the queue
        } catch (exception $e) {
            write_log('Cannot start thread: ' . $e->getMessage(), LOG_NOTICE);
            echo_debug_trace($e, DEBUG_SERVER);
            // it probably will still fail on the reap function, but this shouldn't happen anyway now
        }
    } else { // child
        try {
            $server = NULL;
            $status = QUEUE_RUNNING;
            $is_child = TRUE; // for overriding the shutdown function
            pcntl_signal(SIGTERM, SIG_DFL);
            pcntl_signal(SIGCHLD, SIG_DFL);
            pcntl_signal(SIGINT, SIG_DFL);
            set_error_handler('urdd_error_handler'); // needed as we close stderr... and php will crash if something writes to stderr/out
            $conn_list->close_all(); // needed otherwise quit will not exit if children are running
            $child_db = connect_db(); 
            update_queue_status($child_db, $item->get_dbid(), $status);
            handle_queue_item($child_db, $item);
            // doesn't return here,
            write_log('You should never see this', LOG_ERR);
            exit(NO_ERROR); // for safety tho
        }
        catch (exception $e) {
            write_log('Child Died? ' . $e->getMessage(), LOG_WARNING);
            exit($e->getCode());
        }
    }
    return FALSE;
}


function poll_nzb_dir(DatabaseConnection $db, server_data &$servers)
{
//  echo_debug_function(DEBUG_MAIN, __FUNCTION__);
    $dl_path_basis = get_dlpath($db);
    $dl_path = $dl_path_basis . SPOOL_PATH;
    $users = get_all_users($db);
    foreach ($users as $user) {
        $path = $dl_path . $user . '/';
        if (is_dir($path)) {
            $d = dir($path);
            while (FALSE !== ($entry = $d->read())) {
                if (substr($entry, -4) == '.nzb') {
                        $new_name = find_unique_name($path, '', $entry, '.processing', TRUE);
                    rename($path . $entry, $new_name);
                    queue_parse_nzb($db, $servers, array($new_name), $user);
                }
            }
            $d->close();
        }
    }
}


function shutdown_urdd(DatabaseConnection $db, server_data &$servers)
{
    write_log('Shutting down urdd.', LOG_NOTICE);
    set_config($db, 'urdd_startup', '0');
    do_cancel_all($db, $servers, 'root', TRUE);
    exit(NO_ERROR);
}


function restart_urdd(DatabaseConnection $db, server_data &$servers)
{
    write_log('Restarting urdd.', LOG_NOTICE);
    do_cancel_all($db, $servers, 'root', TRUE);
    posix_kill(posix_getpid(), SIGUSR1);
}


function check_schedule(DatabaseConnection $db, conn_list &$conn_list, server_data &$servers)
{
    // echo_debug_function(DEBUG_MAIN, __FUNCTION__);
    global $config;
    if ($config['scheduler'] !== TRUE)
        return;
    while (($job = $servers->get_first_ready_job($db)) !== FALSE) {
        $action = $job->get_action();
        $line = "{$action->get_command()} {$action->get_args()}";
        $response = '';
        $cmd = do_command($db, $line, $response, $conn_list, $s=NULL, $servers, $action->get_username(), NULL, FALSE);
        if ($cmd == URDD_SHUTDOWN)  // shutdown requested
            shutdown_urdd($db, $servers);
        else if ($cmd == URDD_RESTART)
            restart_urdd($db, $servers);
        $servers->recur_schedule($db, $job); // reschedule the action if needed
    }
}


function read_sockets(DatabaseConnection $db, array $sq, array $listen_sock, conn_list &$conn_list, server_data &$servers)
{
    //  echo_debug_function(DEBUG_MAIN, __FUNCTION__);
    global $responses;
    foreach ($sq as $s) {
        if (in_array($s, $listen_sock)) {
            // this one we can accept on
            $conn = socket_accept($s);
            if ($conn === FALSE)
                socket_error_handler();
            else {
                $c = $conn_list->add_connection($conn);
                write_log("Incoming connection from host {$c->get_peer_hostname()}:{$c->get_peer_port()}", LOG_DEBUG);
                $msg = sprintf($responses[299], get_version());
                $res = socket_write($conn, $msg);
                if ($res === FALSE) { // some error occured, close the connection
                    socket_error_handler();
                    $conn_list->close($conn);
                    continue;
                }
            }
        } else { // established connections
            $conn = & $conn_list->get_conn($s);
            disable_log();
            $line = @socket_read($s, MAX_BUFF_SIZE, PHP_BINARY_READ);
            enable_log();
            if(socket_last_error($s) == 104 || $line === FALSE || $line === '') {
                // EOF or connection closed by peer
                socket_error_handler();
                $conn_list->close($s);
                continue;
            }
            $conn->add_buffer($line);
            $conn->update();
            while (TRUE) {
                $line = $conn->get_buffer_line();
                if ($line === FALSE)  // no full line read
                    break;
                // ok we found a command... do something with it
                echo_debug('read line: [' . preg_replace ('/pass .*/i', 'PASS XXX', preg_replace("/[\n\r]/", '', $line)) . ']', DEBUG_SERVER); // filter out passwords
                $respone = '';
                $cmd = do_command($db, $line, $response, $conn_list, $s, $servers, NULL, NULL, FALSE);
                if ($cmd == URDD_NOCOMMAND) 
                    continue; // no command found, empty line
                $res = @socket_write($s, $response);
                if ($res === FALSE) { // some error occured, close the connection
                    socket_error_handler();
                    $conn_list->close($s);
                    break;
                }
                if ($cmd == URDD_SHUTDOWN)  // shutdown requested
                    shutdown_urdd($db, $servers);
                else if ($cmd == URDD_RESTART)
                    restart_urdd($db, $servers);
                else if ($cmd == URDD_CLOSE_CONN) // quit received
                    $conn_list->close($s);
                if ($cmd != URDD_ERROR)
                    $conn->set_last_cmd ($line);
            }
        }
    }
}


function server(array $listen_sock, DatabaseConnection $db, server_data &$servers)
{
    echo_debug_function(DEBUG_MAIN, __FUNCTION__);
    global $config, $responses;
    $conn_list = new conn_list($config['urdd_timeout']);
    $null_array = array();
    $sock_list = $listen_sock;
    $restart = $config['urdd_restart'];
    restore_old_queue($db, $servers, $conn_list, $restart);
    $username = get_config($db, 'run_update');
    if ($username != '0') { // some install magic, after the install we run update group automagically with the user given by the installer
        set_config($db, 'run_update', '0');
        if (check_user($db, $username) === TRUE) { 
            // run the update groups command when just installed as the main user/admin
            // should do this differently // TODO
            queue_groups($db, $servers, $username);
        } else
            write_log('Invalid user specified for update group list', LOG_NOTICE);
    }
    unset($username);
    set_config($db, 'urdd_startup', '100');
    while (1) {
        $conn_list->close_timedout();
        poll_nzb_dir($db, $servers); // see if there are nzb files in any of the spool directors
        check_schedule($db, $conn_list, $servers); // check if there are scheduled jobs to run
        $servers->check_conn_time(); // see if we need to time-out a connection
        $queue_ready = check_queue($db, $conn_list, $servers) ;
        if ($queue_ready === TRUE) {
            $timeout = $conn_list->first_timeout();
            // queue is empty, we can wait till something arrives
        } else {
            // there is still stuff in the queue, just check if there is any msg
            $timeout = 0;
        }
        $sched_time = $servers->get_first_timeout();
        $sched_timeout = DEFAULT_CHECK_TIMEOUT;
        if ($sched_time !== NULL)
            $sched_timeout = max(0, $sched_time - time());
        if ($timeout === NULL )
            $timeout = $sched_timeout;
        else if ($sched_timeout != 0)
            $timeout = min ($timeout, $sched_timeout);
        $timeout *= 1000000; // to microseconds
        $timeout_us = max(min($timeout, DEFAULT_CHECK_TIMEOUT), 1000); // we at least wait 100 us
        $timeout = 0;
        $sq = array_merge($sock_list, $conn_list->get_fdlist());
        socket_clear_error(); // clear it just in case
        disable_log(); // needed so the select error doesn't show... bad solution, I know but it confuses users
        $res = @socket_select($sq, $null_array, $null_array, 0, $timeout_us); // we always give the timeout in microseconds
        enable_log();
        if ($res === FALSE) {
            if (socket_last_error() == 4) {// child exited, we can schedule some more now
                socket_clear_error();
                continue;
            } else { 
                socket_error_handler();
            }
        } else if ($res == 0)
            //time out but no read... go back to do check queue
            continue;
        else 
            read_sockets($db, $sq, $listen_sock, $conn_list, $servers);
    }
}


function restore_old_queue(DatabaseConnection $db, server_data &$servers, conn_list &$conn_list, $restart)
{
    assert(is_bool($restart));
    echo_debug_function(DEBUG_MAIN, __FUNCTION__);
    try {
        $rstatus = QUEUE_RUNNING;
        $query = "SELECT \"description\", \"ID\", \"username\", \"restart\", \"priority\", \"status\" FROM queueinfo WHERE \"status\" LIKE '$rstatus' ";
        $res_running = $db->execute_query($query);
        $qstatus = QUEUE_QUEUED;
        $pstatus = QUEUE_PAUSED;
        $query = "SELECT \"description\", \"ID\", \"username\", \"status\", \"restart\", \"priority\" FROM queueinfo WHERE (\"status\" LIKE '$pstatus' OR \"status\" LIKE '$qstatus')";
        $res_queued = $db->execute_query($query);
        if ($res_running !== FALSE) {
            $response = '';
            foreach ($res_running as $row) {
                $cmd = $row['description'];
                $priority = $row['priority'];
                $restarted = FALSE;
                if ($restart === TRUE && $row['restart'] > 0) {
                    $username = $row['username'];
                    do_command($db, $cmd, $response, $conn_list, NULL, $servers, $username, $priority, TRUE);
                    $restarted = TRUE;
                }
                $qry = "DELETE FROM queueinfo WHERE \"ID\" = {$row['ID']}";
                $res = $db->execute_query($qry);
                if ($restarted === TRUE)
                    write_log("Restored '$cmd'", LOG_NOTICE);
                else 
                    write_log ("Removed stale '$cmd'", LOG_NOTICE);
            }
        }
        if ($res_queued !== FALSE) {
            $response = '';
            foreach ($res_queued as $row) {
                $cmd = $row['description'];
                $priority = $row['priority'];
                $restarted = FALSE;
                if ($restart === TRUE && $row['restart'] > 0) {
                    $username = $row['username'];
                    do_command($db, $cmd, $response, $conn_list, NULL, $servers, $username, $priority, TRUE);
                    if ($row['status'] == QUEUE_PAUSED) {
                        preg_match_all('/\[([0-9]+)\]/', $response, $match);
                        foreach($match[1] as $m) {
                            if (is_numeric($m[0])) {
                                $arg = array ("{$m[0]}");
                                do_pause($db, $servers, $arg, TRUE, $username);
                                if (compare_command($cmd, COMMAND_DOWNLOAD))
                                    update_dlinfo_status($db, DOWNLOAD_PAUSED, $m[0]);
                                if (compare_command($cmd, COMMAND_POST))
                                    update_postinfo_status($db, POST_PAUSED, $m[0]);
                            }
                        }
                    }
                    $restarted = TRUE;
                }
                $qry = "DELETE FROM queueinfo WHERE \"ID\" = {$row['ID']}";
                $res = $db->execute_query($qry);
                if ($restarted === TRUE)
                    write_log("Restored '$cmd'", LOG_NOTICE);
                else 
                    write_log("Removed stale '$cmd'", LOG_NOTICE);
            }
        }
        $servers->reload_scheduled_jobs($db);
    } catch (exception $e) {
        write_log('Restore failed: ' . $e->getmessage(), LOG_ERR);
    }
}


function listen_socket($address, $address6, $port)
{
    echo_debug_function(DEBUG_MAIN, __FUNCTION__);
    assert (is_numeric($port));
    $success = 0;
    $sockets = array();
    if ($address != '') { // try and create an IPv4 socket first
        $sock = @socket_create(AF_INET, SOCK_STREAM, SOL_TCP);

        if ($sock !== FALSE){
            socket_set_option($sock, SOL_SOCKET, SO_REUSEADDR, 1); 
            $rv = @socket_bind($sock, $address, $port);
            if ($rv === FALSE)
                socket_error_handler(FALSE);
            else  {
                $rv = socket_listen($sock);
                if ($rv === FALSE)
                    socket_error_handler(FALSE);
                else {
                    $success++;
                    $sockets[] = $sock;
                }
            }
        }
    }
    if ($address6 != '' /*&& defined(AF_INET6)*/) {// now try to create an IPv6 socket as well.
        $sock6 = @socket_create(AF_INET6, SOCK_STREAM, SOL_TCP);
        if ($sock6 !== FALSE) {
            socket_set_option($sock6, SOL_SOCKET, SO_REUSEADDR, 1);  
            $rv = @socket_bind($sock6, $address6, $port);
            if ($rv === FALSE)
                socket_error_handler(FALSE);
            else  {
                $rv = socket_listen($sock6);
                if ($rv === FALSE)
                    socket_error_handler(FALSE);
                else {
                    $success++;
                    $sockets[] = $sock6;
                }
            }
        }
    }

    if ($success > 0)
        return $sockets;
    else {
        write_log('No listening sockets could be established', LOG_ERR);
        exit (-1);
    }
}


function set_urdd_userid()
{
    global $config;
    $cur_uid = posix_getuid();
    $cur_euid = posix_geteuid();
    if ($cur_uid == 0 || $cur_euid == 0) {
        write_log('We are started as root. Dropping priviliges', LOG_NOTICE);
        // we are root and must reduce to
        if (!isset($config['urdd_uid']) || !isset($config['urdd_gid']))
            throw new exception ('Urd users or group not set', ERR_CONFIG_ERR);
        if ($config['urdd_gid'] != '') {
            $ginfo = posix_getgrnam($config['urdd_gid']);
            if (!isset($ginfo['gid']))
                throw new exception ("Could not drop priviliges. Group {$config['urdd_gid']} not found.", ERR_GID_NOT_FOUND);
            chgrp($config['log_file'], $ginfo['gid']);
            $rv1 = posix_setgid($ginfo['gid']);
            $rv2 = posix_setegid($ginfo['gid']);
            write_log("Dropping priviliges to group {$config['urdd_gid']}", LOG_NOTICE);

            if ($rv1 === FALSE || $rv2 === FALSE)
                throw new exception ('Could not drop priviliges', ERR_DROP_PRIVS_FAILED);
        }
        if ($config['urdd_uid'] != '') {
            $uinfo = posix_getpwnam($config['urdd_uid']);
            if (!isset($uinfo['uid']))
                throw new exception ("Could not drop priviliges. User {$config['urdd_uid']} not found.", ERR_UID_NOT_FOUND);
            chown($config['log_file'], $uinfo['uid']);
            $rv1 = posix_setuid($uinfo['uid']);
            $rv2 = posix_seteuid($uinfo['uid']);
            write_log("Dropping priviliges to user {$config['urdd_uid']}", LOG_NOTICE);
            if ($rv1 === FALSE || $rv2 === FALSE)
                throw new exception ('Could not drop priviliges', ERR_DROP_PRIVS_FAILED);
        }
    } else {
        posix_seteuid(posix_getuid());
        posix_setegid(posix_getgid());
    }
}


function verify_php_version()
{
    if (!version_compare(phpversion(), '5.1.2', '>='))
        throw new exception ('PHP version too old. 5.1.2 or better must be used', ERR_CONFIG_ERROR);
}


function verify_safe_mode() 
{
    $safe_mode = ini_get('safe_mode');
    if ($safe_mode == 'on')
        throw new exception ('Safe mode must be DISabled in ' . php_ini_loaded_file(), ERR_CONFIG_ERROR);
}


function stupid_php_crap()
{
    if (version_compare(PHP_VERSION, '5.3.0') < 0) 
        set_magic_quotes_runtime(FALSE); // some crap about magic quotes...  considered harmful tho
    date_default_timezone_set(date_default_timezone_get()); // silly but otherwise we get tons of notices for each time fn call with E_STRICT
    declare(ticks = 1); // we need this for the signals to work properly.... all hail the splendid php documentation :-|
}


function verify_installed()
{
    clearstatcache(); // we want to be sure, so cache values are flushed.
    $path = realpath(dirname(__FILE__) . '/../' . '.installed');
    if (!file_exists($path))
        throw new exception('URDD is not installed properly. Please run the installer', ERR_CONFIG_ERROR);
}


function find_server(DatabaseConnection $db, server_data $servers, test_result_list $test_results)
{
    global $config;
    $servers->find_servers($db, $test_results, NULL, ($config['find_servers_type'] == 'extended'), TRUE);
    echo "\n\nResults:\n";
    echo $test_results->get_all_as_string();
  //  exit(0);
}


function get_server_data(DatabaseConnection $db)
{
    global $config;
    $urdd_maxthreads = get_config($db, 'urdd_maxthreads'); // total number of threads allowed
    $nntp_maxthreads = get_config($db, 'nntp_maxthreads'); // total number of threads that connect to a news server allowed
    $db_intensive_maxthreads = get_config($db, 'db_intensive_maxthreads'); // total number of threads that are marked db intensive
    if ($db_intensive_maxthreads == 0)
        $db_intensive_maxthreads = $urdd_maxthreads; // if we set it to 0, we mean no limit, which is limited by the actual max threads :)
    $servers = new server_data($config['urdd_queue_size'], $nntp_maxthreads, $urdd_maxthreads, $db_intensive_maxthreads);  
    return $servers;
}


//////// Code: ////////////


// enable asserts
set_assert(TRUE);
// disable asserts
//set_assert(FALSE);


// make PHP verbose
ini_set('display_errors', 1);
ini_set('log_errors', 1);
ini_set('error_reporting' , E_ALL|E_STRICT);

$config['disable_nntp'] = TRUE;

try {
    verify_installed();
    $test_results = new test_result_list();

    verify_php_version();
    verify_safe_mode();

    stupid_php_crap();
} catch (exception $e) {
    echo $e->getmessage() . "\n";
    exit($e->getcode());
}
try {
    $start_time = time();
    set_time_limit(0);
    read_options();
    if (isset($config['urdd_logfile'])) 
        $config['log_file'] = $config['urdd_logfile'];
    else 
        $config['log_file'] = '/dev/null';
    verify_bool('urdd_daemonise', $config);
    $log_options = get_logoption($config['urdd_log']);
} catch (exception $e) {
    if ($e->getcode () == COMMANDLINE_ERROR)
        write_log('Incorrect commandline option: '. $e->getmessage(), LOG_ERR);
    else 
        write_log ('Config error? ' . $e->getmessage(), LOG_ERR);
    exit ($e->getcode());
}

try {
    write_log('Starting urdd', LOG_NOTICE);
    check_modules($test_results);
} catch (exception $e) {
    echo $e->getmessage(), "\n"; // must be echo, cause if it fails the log file cannot be written to
    exit ($e->getcode());
}

if ($config['urdd_daemonise'] === TRUE)
    daemonise(); // start it as a daemon process

$is_child = FALSE; // this is the main start up, so we are always the parent

try {
    if (isset($config['urdd_pidfile'])) {
        try {
            check_pid_file($config['urdd_pidfile']);
        } catch (exception $e) {
            if (!isset($config['force_pidfile']) || $config['force_pidfile'] !== TRUE) {
                write_log($e->getmessage(), LOG_ERR);
                die;
            }
            delete_pid_file($config['urdd_pidfile']);
        }
        set_pid_file($config['urdd_pidfile']);
    } else {
        write_log('Update config.php: add line $config[\'urdd_pidfile\'] =  $pathc . \'/urdd.pid\'', LOG_ERR);
    }

    $db = connect_db();
    register_shutdown_function('status_shutdown_handler'); // so that the startup status will be reset in case of urdd termination during startup
    set_config($db, 'urdd_startup', '1');
    
    verify_config($db, $test_results);
    set_config($db, 'urdd_startup', '10');
    verify_magpie_cache_dir($test_results);
    verify_memory_limit($test_results);
    set_config($db, 'urdd_startup', '20');
    set_db_version($db);
    $config ['find_servers'] = (isset($config['find_servers']) && $config['find_servers'] === TRUE) ? TRUE: FALSE; // force value to be set
    $servers = get_server_data($db);  
    try { 
        $servers->load_servers($db, $test_results, FALSE);
    } catch (exception $e) {
        if ($e->getcode() == ERR_NO_ACTIVE_SERVER)
            write_log($e->getmessage(), LOG_ERR);
        if ($config ['find_servers'] === FALSE) // if we are gonna run find servers next, we don't want to exit
         ;//   exit ($e->getcode()); // may have to remove this if we want to run find_servers from the website
    }
    set_config($db, 'urdd_startup', '50');
    
    if ($config ['find_servers'] === TRUE) 
        find_server($db, $servers, $test_results);
    if (isset($config['test_servers']) && $config['test_servers'] === TRUE) {
        $servers->test_servers($db, $test_results);
    }
    if (!isset($config['test_urdland']) || $config['test_urdland'] === TRUE)
        test_urdland_connection($db, $test_results);

    set_config($db, 'urdd_startup', '80');
    set_handlers();
} catch (exception $e) {
    write_log('An error occured during startup of URD daemon: ' . $e->getmessage(), LOG_CRIT);
    echo_debug_trace($e, DEBUG_SERVER);
    exit($e->getcode());
}

try {
    $sock = listen_socket($config['urdd_listen_host'], $config['urdd_listen_host6'], $config['urdd_port']);
    set_config($db, 'urdd_startup', '90');
    set_urdd_userid();
    $config['disable_nntp'] = FALSE;
    set_config($db, 'urdd_startup', '99');
    server($sock, $db, $servers); // actually start the urd daemon
} catch (exception $e) {
    if ($e->getcode() == ERR_RESTART_REQUESTED) {
        $pid = pcntl_fork();
        if ($pid < 0) {  // error
            write_log('Daemonise failed', LOG_ERR);
            exit(INTERNAL_ERROR) ;
        } else if ($pid != 0) // parent
            exit(NO_ERROR) ; // parent exits
        else 
            start_urdd();
    } else {
        write_log ('An error occured during startup of URD daemon: ' . $e->getmessage(), LOG_CRIT);
        write_log ('is there another URD daemon running?', LOG_NOTICE);
        echo_debug_trace($e, DEBUG_SERVER);
        exit($e->getcode());
    }
}


?>
